﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.RuntimeReflectionExtensions;
using System.Text;
using System.Linq;

namespace LeRenard
{
    /// <summary>
    /// Provides extension methods to interact with <see cref="Type"/> objects.
    /// </summary>
    public static class TypeExtensions
    {
        #region Friendly Name
        /// <summary>
        /// Caches friendly names of a <see cref="Type"/>.
        /// Use method GetFriendlyName(Type type) - do not directly access this object.
        /// </summary>
        [ThreadStatic]
        private static IDictionary<int, string> _friendlyNameCache;

        /// <summary>
        /// Gets the friendly name of a <see cref="Type"/> from the cache.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> to get a friendly name for.</param>
        /// <returns>The friendly name of a <see cref="Type"/>.</returns>
        private static string GetFriendlyName(Type type)
        {
            var typeInfo = type.GetTypeInfo();

            if (!typeInfo.IsGenericType) {
                return type.Name;
            }

            if (_friendlyNameCache == null) {
                _friendlyNameCache = new Dictionary<int, string>();
            }

            var key = type.GetHashCode();
            if (!_friendlyNameCache.ContainsKey(key)) { 
                _friendlyNameCache.Add(key, BuildFriendlyName(type));
            }

            return _friendlyNameCache[key];
        }

        /// <summary>
        /// Builds the <see cref="Type"/>'s friendly name string.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> to build a friendly name for.</param>
        /// <returns>The friendly name of a <see cref="Type"/>.</returns>
        private static string BuildFriendlyName(Type type)
        {
            var typeInfo = type.GetTypeInfo();

            if (!typeInfo.IsGenericType) {
                return type.Name;
            }

            var name = new StringBuilder();

            if (type.Name.IndexOf('`') > 0) {
                name.Append(type.Name.Remove(type.Name.IndexOf('`')));
            } else {
                name.Append(type.Name);
            }

            name.AppendFormat("<{0}>", type.GenericTypeArguments.Select(t => t.ToFriendlyName()).Flatten(", ", String.Empty, String.Empty));

            return name.ToString();
        }

        /// <summary>
        /// Returns the friendly name of a <see cref="Type"/>.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> to return a friendly name for.</param>
        /// <returns>The friendly name of a <see cref="Type"/>.</returns>
        /// <exception cref="ArgumentNullException">When <paramref name="type"/> is null.</exception>
        public static string ToFriendlyName(this Type type)
        {
            ThrowIf.Null(() => type);

            return GetFriendlyName(type);
        }
        #endregion Friendly Name
    }
}
